namespace Dapper.FastCrud.Mappings
{
    using Dapper.FastCrud.Mappings.Registrations;
    using Dapper.FastCrud.Validations;
    using System;
    using System.ComponentModel.DataAnnotations.Schema;

    /// <summary>
    /// Used to easily set up mappings for entity properties.
    /// </summary>
    public class PropertyMapping<TEntityType>
    {
        private readonly PropertyRegistration _propertyRegistration;

        /// <summary>
        /// Default constructor.
        /// </summary>
        internal PropertyMapping(PropertyRegistration propertyRegistration)
        {
            Validate.NotNull(propertyRegistration, nameof(propertyRegistration));
            _propertyRegistration = propertyRegistration;
        }

        /// <summary>
        /// Gets the underlying property registration.
        /// </summary>
        internal PropertyRegistration Registration => _propertyRegistration;

        /// <summary>
        /// Gets the property name.
        /// </summary>
        public string PropertyName => _propertyRegistration.PropertyName;

        /// <summary>
        /// Marks the property as primary key.
        /// </summary>
        public PropertyMapping<TEntityType> SetPrimaryKey(bool isPrimaryKey = true)
        {
            _propertyRegistration.IsPrimaryKey = isPrimaryKey;
            return this;
        }

        /// <summary>
        /// Sets the database column name.
        /// </summary>
        public PropertyMapping<TEntityType> SetDatabaseColumnName(string dbColumnName)
        {
            Validate.NotNullOrEmpty(dbColumnName, nameof(dbColumnName));

            _propertyRegistration.DatabaseColumnName = dbColumnName;
            return this;
        }

        /// <summary>
        /// Indicates that the property is mapped to a database generated field.
        /// This does not cover default values generated by the database (please use <see cref="ExcludeFromInserts"/> and <see cref="RefreshOnInserts"/> for this scenario).
        /// </summary>
        public PropertyMapping<TEntityType> SetDatabaseGenerated(DatabaseGeneratedOption option)
        {
            switch (option)
            {
                case DatabaseGeneratedOption.Computed:
                    _propertyRegistration.IsExcludedFromInserts = true;
                    _propertyRegistration.IsExcludedFromUpdates = true;
                    _propertyRegistration.IsRefreshedOnInserts = true;
                    _propertyRegistration.IsRefreshedOnUpdates = true;
                    break;
                case DatabaseGeneratedOption.Identity:
                    _propertyRegistration.IsExcludedFromInserts = true;
                    _propertyRegistration.IsExcludedFromUpdates = true;
                    _propertyRegistration.IsRefreshedOnInserts = true;
                    _propertyRegistration.IsRefreshedOnUpdates = false;
                    break;
                case DatabaseGeneratedOption.None:
                    _propertyRegistration.IsExcludedFromInserts = false;
                    _propertyRegistration.IsExcludedFromUpdates = false;
                    _propertyRegistration.IsRefreshedOnInserts = false;
                    _propertyRegistration.IsRefreshedOnUpdates = false;
                    break;
                default:
                    throw new NotSupportedException($"Option {option} is not supported.");
            }
            return this;
        }

        /// <summary>
        /// Sets the column order, normally used for matching foreign keys with the primary composite keys.
        /// </summary>
        public PropertyMapping<TEntityType> SetColumnOrder(int columnOrder)
        {
            _propertyRegistration.ColumnOrder = columnOrder;
            return this;
        }

        /// <summary>
        /// Indicates that the property gets refreshed on INSERTs.
        /// </summary>
        public PropertyMapping<TEntityType> RefreshOnInserts(bool refreshOnInsert = true)
        {
            _propertyRegistration.IsRefreshedOnInserts = refreshOnInsert;
            return this;
        }

        /// <summary>
        /// Indicates that the property gets refreshed on UPDATEs.
        /// </summary>
        public PropertyMapping<TEntityType> RefreshOnUpdates(bool refreshOnUpdate = true)
        {
            _propertyRegistration.IsRefreshedOnUpdates = refreshOnUpdate;
            return this;
        }

        /// <summary>
        /// The property will be included in insert operations.
        /// </summary>
        public PropertyMapping<TEntityType> IncludeInInserts(bool includeInInserts = true)
        {
            _propertyRegistration.IsExcludedFromInserts = !includeInInserts;
            return this;
        }

        /// <summary>
        /// The property will be included in update operations.
        /// </summary>
        public PropertyMapping<TEntityType> IncludeInUpdates(bool includeInUpdates = true)
        {
            _propertyRegistration.IsExcludedFromUpdates = !includeInUpdates;
            return this;
        }


        /// <summary>
        /// Removes the current property mapping.
        /// </summary>
        public void Remove()
        {
            _propertyRegistration.EntityMapping.RemoveProperty(_propertyRegistration.PropertyName);
        }

        #region Obsolete methods and properties
        [Obsolete("This method will be removed. Use IncludeInInserts(false) instead", error: false)]
        public PropertyMapping<TEntityType> ExcludeFromInserts()
        {
            _propertyRegistration.IsExcludedFromInserts = true;
            return this;
        }

        [Obsolete("This method will be removed. Use IncludeInUpdates(false) instead", error: false)]
        public PropertyMapping<TEntityType> ExcludeFromUpdates()
        {
            _propertyRegistration.IsExcludedFromUpdates = true;
            return this;
        }

        [Obsolete(message: "This method will be removed in a future version. Please use the relationship setup methods on the entity mapping instead.", error: false)]
        public PropertyMapping<TEntityType> SetChildParentRelationship<TParentEntityType>(string referencingEntityPropertyName)
        {
            //TODO: restore the functionality of this method for the full release
            throw new NotSupportedException();
        }

        [Obsolete(message:"This property will be removed. Use SetPrimaryKey instead.", error:false)]
        public bool IsPrimaryKey
        {
            get => _propertyRegistration.IsPrimaryKey;
            set => _propertyRegistration.IsPrimaryKey = value;
        }

        [Obsolete(message: "This property will be removed. Use RefreshOnInserts instead.", error: false)]
        public bool IsRefreshedOnInserts
        {
            get => _propertyRegistration.IsRefreshedOnInserts;
            set => _propertyRegistration.IsRefreshedOnInserts = value;
        }

        [Obsolete(message: "This property will be removed. Use RefreshOnUpdates instead.", error: false)]
        public bool IsRefreshedOnUpdates
        {
            get => _propertyRegistration.IsRefreshedOnUpdates;
            set => _propertyRegistration.IsRefreshedOnUpdates = value;
        }

        [Obsolete(message: "This property will be removed. Use IncludeInInserts(false) instead.", error: false)]
        public bool IsExcludedFromInserts
        {
            get => _propertyRegistration.IsExcludedFromInserts;
            set => _propertyRegistration.IsExcludedFromInserts = value;
        }

        [Obsolete(message: "This property will be removed. Use IncludeInUpdates(false) instead.", error: false)]
        public bool IsExcludedFromUpdates
        {
            get => _propertyRegistration.IsExcludedFromUpdates;
            set => _propertyRegistration.IsExcludedFromUpdates = value;
        }

        #endregion

    }
}
